from __future__ import annotations

import asyncio
from typing import List, Optional

from fastapi import Depends, FastAPI, File, Form, HTTPException, Query, UploadFile, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from pydantic import BaseModel, Field

from .auth import require_token
from .services import (
    AccountsService,
    AttachmentService,
    EventDTO,
    MentionService,
    NotificationService,
    SearchService,
    ServiceContext,
    SocialService,
    TaskService,
    TimelineService,
)


class EventModel(BaseModel):
    id: str
    type: str
    owner: str
    actor: str
    created_at: str
    headers: dict
    content: str
    attachments: List[str] = Field(default_factory=list)

    @classmethod
    def from_dto(cls, dto: EventDTO) -> "EventModel":
        return cls(
            id=dto.id,
            type=dto.type,
            owner=dto.owner,
            actor=dto.actor,
            created_at=dto.created_at,
            headers=dto.headers,
            content=dto.content,
            attachments=dto.attachments,
        )


class TaskModel(BaseModel):
    id: str
    title: str
    description: str
    status: str
    created_at: str
    actor: str
    assignees: List[str] = Field(default_factory=list)


class TaskStatusResponse(BaseModel):
    id: str
    status: str


class NotificationModel(BaseModel):
    type: str
    actor: str
    created_at: str
    summary: str


class MentionModel(BaseModel):
    id: str
    type: str
    actor: str
    created_at: str
    snippet: str


class AccountModel(BaseModel):
    id: str
    display_name: str
    type: str
    created_at: str


def _split_csv(value: Optional[str]) -> Optional[List[str]]:
    if value is None:
        return None
    parts = [p.strip() for p in value.split(",") if p.strip()]
    return parts or None


def create_app(ctx: Optional[ServiceContext] = None) -> FastAPI:
    ctx = ctx or ServiceContext()

    timeline = TimelineService(ctx)
    social = SocialService(ctx)
    tasks = TaskService(ctx)
    notifications = NotificationService(ctx)
    mentions = MentionService(ctx)
    attachments = AttachmentService(ctx)
    search = SearchService(ctx)
    accounts_service = AccountsService()

    app = FastAPI(title="Agent Workspace Web API", version="0.1.0")
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    @app.get("/healthz")
    async def healthz() -> dict:
        return {"status": "ok"}

    @app.get("/api/v1/owners/{owner}/feed", response_model=List[EventModel])
    async def get_feed(
        owner: str,
        actors: Optional[str] = Query(default=None, description="Comma-separated actor ids"),
        tags: Optional[str] = Query(default=None),
        mentions_q: Optional[str] = Query(default=None, alias="mentions"),
        types: Optional[str] = Query(default=None),
        since: Optional[str] = Query(default=None),
        until: Optional[str] = Query(default=None),
        limit: Optional[int] = Query(default=None, ge=1, le=500),
        _: None = Depends(require_token),
    ) -> List[EventModel]:
        actor_list = _split_csv(actors)
        tag_list = _split_csv(tags)
        type_list = _split_csv(types)
        events = timeline.feed(
            owner,
            actors=actor_list,
            tags=tag_list,
            mentions=mentions_q,
            types=type_list,
            since=since,
            until=until,
            limit=limit,
        )
        return [EventModel.from_dto(e) for e in events]

    @app.get("/api/v1/owners/{owner}/events/{event_id}", response_model=EventModel)
    async def get_event(owner: str, event_id: str, _: None = Depends(require_token)) -> EventModel:
        event = timeline.get(owner, event_id)
        if not event:
            raise HTTPException(status_code=404, detail="event not found")
        return EventModel.from_dto(event)

    @app.post("/api/v1/owners/{owner}/posts", response_model=EventModel)
    async def create_post(
        owner: str,
        actor: str = Form(...),
        message: str = Form(...),
        tags: Optional[str] = Form(default=None),
        mentions_form: Optional[str] = Form(default=None, alias="mentions"),
        files: Optional[List[UploadFile]] = File(default=None),
        _: None = Depends(require_token),
    ) -> EventModel:
        tag_list = _split_csv(tags)
        mention_list = _split_csv(mentions_form)
        upload_payload = []
        if files:
            for upload in files:
                data = await upload.read()
                filename = upload.filename or "attachment"
                upload_payload.append((filename, data))
        event = social.create_post(
            owner,
            actor,
            message,
            tags=tag_list,
            mentions=mention_list,
            attachments=upload_payload if upload_payload else None,
        )
        return EventModel.from_dto(event)

    @app.post("/api/v1/owners/{owner}/comments", response_model=EventModel)
    async def create_comment(
        owner: str,
        actor: str = Form(...),
        post_id: str = Form(...),
        message: str = Form(...),
        _: None = Depends(require_token),
    ) -> EventModel:
        event = social.create_comment(owner, actor, post_id, message)
        return EventModel.from_dto(event)

    @app.post("/api/v1/owners/{owner}/reactions", response_model=EventModel)
    async def create_reaction(
        owner: str,
        actor: str = Form(...),
        post_id: str = Form(...),
        reaction: str = Form(...),
        _: None = Depends(require_token),
    ) -> EventModel:
        event = social.create_reaction(owner, actor, post_id, reaction)
        return EventModel.from_dto(event)

    @app.get("/api/v1/owners/{owner}/tasks", response_model=List[TaskModel])
    async def list_tasks(
        owner: str,
        assignee: Optional[str] = Query(default=None),
        status: Optional[str] = Query(default=None),
        _: None = Depends(require_token),
    ) -> List[TaskModel]:
        results = tasks.list_tasks(owner, assignee=assignee, status=status)
        return [TaskModel(**item) for item in results]

    @app.post("/api/v1/owners/{owner}/tasks", response_model=EventModel)
    async def create_task(
        owner: str,
        actor: str = Form(...),
        title: str = Form(...),
        description: str = Form(...),
        assignees: Optional[str] = Form(default=None),
        _: None = Depends(require_token),
    ) -> EventModel:
        assignee_list = _split_csv(assignees)
        event = tasks.create_task(owner, actor, title, description, assignee_list)
        return EventModel.from_dto(event)

    @app.post("/api/v1/owners/{owner}/tasks/{task_id}/status", response_model=TaskStatusResponse)
    async def update_task_status(
        owner: str,
        task_id: str,
        actor: str = Form(...),
        status: str = Form(...),
        _: None = Depends(require_token),
    ) -> TaskStatusResponse:
        payload = tasks.update_status(owner, actor, task_id, status)
        return TaskStatusResponse(**payload)

    @app.post("/api/v1/owners/{owner}/tasks/{task_id}/comments", response_model=EventModel)
    async def comment_on_task(
        owner: str,
        task_id: str,
        actor: str = Form(...),
        message: str = Form(...),
        _: None = Depends(require_token),
    ) -> EventModel:
        event = tasks.add_comment(owner, actor, task_id, message)
        return EventModel.from_dto(event)

    @app.get("/api/v1/owners/{owner}/notifications", response_model=List[NotificationModel])
    async def list_notifications(
        owner: str,
        target: Optional[str] = Query(default=None),
        last_hours: int = Query(default=24, ge=1, le=168),
        _: None = Depends(require_token),
    ) -> List[NotificationModel]:
        recipient = target or owner
        items = notifications.list(owner, recipient, last_hours)
        return [NotificationModel(**item) for item in items]

    @app.get("/api/v1/owners/{owner}/mentions", response_model=List[MentionModel])
    async def list_mentions(
        owner: str,
        target: Optional[str] = Query(default=None),
        since: Optional[str] = Query(default=None),
        until: Optional[str] = Query(default=None),
        _: None = Depends(require_token),
    ) -> List[MentionModel]:
        recipient = target or owner
        items = mentions.list_mentions(owner, recipient, since=since, until=until)
        return [MentionModel(**item) for item in items]

    @app.get("/api/v1/owners/{owner}/attachments/{event_id}")
    async def list_attachments(
        owner: str,
        event_id: str,
        _: None = Depends(require_token),
    ) -> JSONResponse:
        files = attachments.list_for_event(event_id)
        return JSONResponse({"event_id": event_id, "files": files})

    @app.get("/api/v1/owners/{owner}/attachments/{event_id}/{filename}")
    async def download_attachment(
        owner: str,
        event_id: str,
        filename: str,
        _: None = Depends(require_token),
    ) -> FileResponse:
        try:
            path = attachments.open_file(event_id, filename)
        except FileNotFoundError as exc:
            raise HTTPException(status_code=404, detail=str(exc)) from exc
        return FileResponse(path)

    @app.get("/api/v1/accounts", response_model=List[AccountModel])
    async def list_accounts(_: None = Depends(require_token)) -> List[AccountModel]:
        store = accounts_service.list_accounts()
        return [
            AccountModel(
                id=acc.id,
                display_name=acc.display_name,
                type=acc.type,
                created_at=acc.created_at,
            )
            for acc in store.accounts.values()
        ]

    @app.get("/api/v1/search", response_model=List[EventModel])
    async def search_events(
        owner: str,
        query: str = Query(..., min_length=1),
        actors: Optional[str] = Query(default=None),
        limit: Optional[int] = Query(default=50, ge=1, le=500),
        _: None = Depends(require_token),
    ) -> List[EventModel]:
        actor_list = _split_csv(actors)
        results = search.search(owner, query, actors=actor_list, limit=limit)
        return [EventModel.from_dto(ev) for ev in results]

    @app.websocket("/ws/owners/{owner}/feed")
    async def feed_socket(websocket: WebSocket, owner: str) -> None:
        await websocket.accept()
        try:
            snapshot = timeline.feed(owner, limit=50)
            await websocket.send_json(
                {
                    "type": "snapshot",
                    "events": [EventModel.from_dto(e).dict() for e in snapshot],
                }
            )
            # Basic heartbeat every 10 seconds. Real-time file watching can plug in later.
            while True:
                await asyncio.sleep(10)
                await websocket.send_json({"type": "heartbeat"})
        except WebSocketDisconnect:
            return

    return app


__all__ = ["create_app"]
